package driver

import (
	"context"
	"errors"
	"fmt"
	"log/slog"
	"math/rand/v2"
	"slices"
	"sync"
	"time"

	"gitee.com/gousing/cache/internal"
	"github.com/redis/go-redis/v9"
)

var (
	ErrRedisLockRepeated   = errors.New("cache redisLock: tryLock of repeated locking")
	ErrRedisLockTimeout    = errors.New("cache redisLock: tryLock timeout to acquire")
	ErrRedisLockRecycled   = errors.New("cache redisLock: tryLock of recycled")
	ErrRedisLockFailed     = errors.New("cache redisLock: tryLock failed to acquire")
	ErrRedisUnlockFailed   = errors.New("cache redisLock: unlock failed to release")
	ErrRedisUnlockReleased = errors.New("cache redisLock: unlock of repeated release")
)

type redisLock struct {
	client           redis.UniversalClient
	ctx              context.Context
	lockPre          []byte        // 锁前缀
	defaultTimeout   time.Duration // 默认5秒, 锁自动回收延时时间, 防止锁申请超时及异常时自动释放( lockEntry 依据用户timeout自动计算)
	minRetryInterval time.Duration // 默认5毫秒, 最小重试间隔时间, 最小值不能小于5毫秒，最大值不能超过10毫秒( lockEntry 依据用户timeout自动计算)
	maxRetryInterval time.Duration // 默认50毫秒, 最大重试间隔时间, 最小值不能小于50毫秒，最大值不能超过100毫秒( lockEntry 依据用户timeout自动计算)
	entryPool        sync.Pool
}

func newRedisLock(client redis.UniversalClient, lockPre string) *redisLock {
	rc := &redisLock{
		client:           client,
		ctx:              context.Background(),
		lockPre:          []byte(lockPre),
		defaultTimeout:   time.Second * 5,
		minRetryInterval: time.Millisecond * 5,
		maxRetryInterval: time.Millisecond * 50,
	}
	rc.entryPool = sync.Pool{
		New: func() any {
			entry := &redisLockEntry{
				key:   make([]byte, 0, 128),
				token: make([]byte, 0, 16),
			}
			entry.key = append(entry.key, lockPre...)
			return entry
		},
	}
	return rc
}

// NewDefault 创建一个新的 redisLockEntry 实例, 并设置默认的超时时间(5秒)
func (rc *redisLock) NewDefault(requestKey string) *redisLockEntry {
	return rc.NewTimeout(requestKey, rc.defaultTimeout)
}

// NewTimeout 创建一个新的 redisLockEntry 实例，并设置超时时间, 如果请求的超时时间小于1秒，则设置为默认的1秒
func (rc *redisLock) NewTimeout(requestKey string, requestTimeout time.Duration) *redisLockEntry {
	entry := rc.entryPool.Get().(*redisLockEntry)
	entry.rc = rc
	entry.key = entry.key[:len(rc.lockPre)]
	entry.key = append(entry.key, requestKey...)
	entry.token = entry.token[:0]
	if err := internal.RandAppend(&entry.token); err != nil {
		entry.token = append(entry.token, fmt.Sprintf("%d", time.Now().UnixNano())...)
		if len(entry.token) > 16 {
			entry.token = entry.token[:16]
			entry.token = slices.Clip(entry.token)
		}
	}
	entry.timeout = max(requestTimeout, time.Second)
	if entry.timeout > time.Second {
		entry.minRetryInterval = min(max(requestTimeout/256, time.Millisecond*10), rc.minRetryInterval) // 最小值不能小于 5 毫秒，最大值不能超过 10 毫秒
		entry.maxRetryInterval = min(max(requestTimeout/32, time.Millisecond*100), rc.maxRetryInterval) // 最小值不能小于 50 毫秒，最大值不能超过 100 毫秒
	} else {
		entry.minRetryInterval = rc.minRetryInterval
		entry.maxRetryInterval = rc.maxRetryInterval
	}
	entry.isLocked = false
	entry.isRecycled = false
	return entry
}

// DefaultTimeout 获取默认的超时时间(5秒)
func (rc *redisLock) DefaultTimeout() time.Duration {
	return rc.defaultTimeout
}

type redisLockEntry struct {
	rc      *redisLock
	key     []byte
	token   []byte
	timeout time.Duration
	// 重试配置
	minRetryInterval time.Duration
	maxRetryInterval time.Duration
	// 内部状态
	isLocked   bool
	isRecycled bool
}

// Lua 脚本：只有 token 匹配才删除
const redisUnlockLua = `
    if redis.call("GET", KEYS[1]) == ARGV[1] then
        return redis.call("DEL", KEYS[1])
    else
        return 0
    end
`

// 预加载脚本
var redisUnlockScript = redis.NewScript(redisUnlockLua)

func (s *redisLockEntry) getKey() string {
	return string(s.key)
}
func (s *redisLockEntry) getToken() string {
	return string(s.token)
}

// TryLock 获取锁，阻塞直到成功或上下文超时
func (s *redisLockEntry) TryLock() error {
	if s == nil {
		return ErrRedisLockFailed
	}
	if s.isLocked {
		return ErrRedisLockRepeated
	}
	if s.isRecycled {
		return ErrRedisLockRecycled
	}
	timeStart := time.Now()

	ctx, cancel := context.WithTimeout(s.rc.ctx, s.timeout)
	defer cancel()
	var (
		acquired bool
		attempt  int
		retry    int
		err      error
		timer    *time.Timer
		backoff  time.Duration
	)
	for {
		select {
		case <-ctx.Done():
			if timer != nil {
				if !timer.Stop() {
					<-timer.C
				}
			}
			internal.GetLogger().Error("cache: redisLock TryLock timeout",
				slog.String("key", s.getKey()),
				slog.String("timeout", s.timeout.String()),
				slog.String("since", time.Since(timeStart).String()),
				slog.Int("retry", retry))
			return ErrRedisLockTimeout
		default:
			// 获取锁, 自动释放时间为用户timeout+1s的缓冲时间以避免超时后过早的自动释放锁
			acquired, err = s.rc.client.SetNX(ctx, s.getKey(), s.getToken(), s.timeout+time.Second).Result()
			if err != nil {
				if retry > 0 {
					internal.GetLogger().Info("cache: redisLock TryLock SetNX failed",
						slog.String("key", s.getKey()),
						slog.String("since", time.Since(timeStart).String()),
						slog.Int("retry", retry),
						slog.String("error", err.Error()))
				}
				retry++
			}
			if acquired {
				s.isLocked = true
				return nil
			}
			// 指数退避
			attempt = max(attempt, 20)
			backoff = s.minRetryInterval * time.Duration(1<<attempt)
			if backoff > s.maxRetryInterval {
				backoff = s.maxRetryInterval
				attempt = 0
			} else {
				attempt++
			}
			// 5%~25%的随机抖动（避免惊群效应）
			jitter := rand.Float64()*0.25 + 0.05
			backoff = time.Duration(float64(backoff) * (1 + jitter))
			timer = time.NewTimer(backoff)
			select {
			case <-ctx.Done():
				if !timer.Stop() {
					<-timer.C
				}
				internal.GetLogger().Error("cache: redisLock TryLock timeout",
					slog.String("key", s.getKey()),
					slog.String("timeout", s.timeout.String()),
					slog.String("since", time.Since(timeStart).String()),
					slog.Int("retry", retry))
				return ErrRedisLockTimeout
			case <-timer.C:
			}

			// debug log
			// fmt.Println("redisLock TryLock: ", s.getKey(), "token:", s.getToken(), "retry", retry, "attempts:", attempt, "backoff:", backoff)
		}
	}
}

// Unlock 释放锁（释放后还能再次上锁）
func (s *redisLockEntry) Unlock() error {
	if s != nil && s.isLocked {
		// debug log
		// fmt.Println("redisLock Unlock: ", s.getKey(), "token:", s.getToken())
		s.isLocked = false
		// 执行预加载的 Lua 脚本
		res, err := redisUnlockScript.Run(s.rc.ctx, s.rc.client, []string{s.getKey()}, s.getToken()).Result()
		if err != nil {
			if err != redis.Nil {
				internal.GetLogger().Error("RedisLock Unlock failed", slog.String("key", s.getKey()), slog.String("error", err.Error()))
				return err
			}
			return ErrRedisUnlockFailed
		}
		if res == int64(0) {
			return ErrRedisUnlockFailed // 主动释放失败，可能 Redis 端已过期自动释放了锁
		}
	}
	return nil
}

// UnlockAndPutPool 释放锁并并回收锁到Pool资源池（幂等，只释放一次，回收后无法再次使用）
func (s *redisLockEntry) UnlockAndPutPool() error {
	if s != nil {
		err := s.Unlock()
		s.PutPool()
		return err
	}
	return nil
}

// PutPool 释放并回收锁到Pool资源池
func (s *redisLockEntry) PutPool() {
	if s != nil && !s.isRecycled {
		// debug log
		// fmt.Println("redisLock PutPool: ", s.getKey(), "token:", s.getToken())
		s.key = s.key[:len(s.rc.lockPre)]
		s.token = s.token[:0]
		s.isRecycled = true
		s.rc.entryPool.Put(s)
	}
}
